<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div>
    <h2>一、作用</h2>
    <div>call、apply、bind作用是改变函数执行时的上下文，简而言之就是改变函数运行时的this指向</div>
    <script>
      // var name = "lucy";
      // const obj = {
      //   name: "martin",
      //   say: function () {
      //     console.log(this.name);
      //   }
      // };
      // obj.say(); //martin，this指向obj对象
      // // setTimeout 是在全局执行上下文的环境中执行的，这时候this指向window，所以输出luck
      // setTimeout(obj.say, 0); //lucy，this指向window对象

      // // 我们实际需要的是this指向obj对象，这时候就需要该改变this指向了
      // setTimeout(obj.say.bind(obj),0); //martin，this指向obj对象
    </script>

    <h2>二、区别</h2>
    <h4>apply</h4>
    <div>apply接受两个参数，第一个参数是this的指向，第二个参数是函数接受的参数，以数组的形式传入</div>
    <br>
    <div>改变this指向后原函数会立即执行，且此方法只是临时改变this指向一次</div>
    <br>
    <div>当第一个参数为null、undefined的时候，默认指向window(在浏览器中)</div>
    <script>
      // function fn(...args){
      //     console.log(this,args);
      // }
      // let obj = {
      //     myname:"张三"
      // }

      // // fn.apply(obj,[1,2]); // this会变成传入的obj，传入的参数必须是一个数组；
      // // fn(1,2) // this指向window
      // // 当第一个参数为null、undefined的时候，默认指向window(在浏览器中)
      // fn.apply(null,[1,2]); // this指向window
      // fn.apply(undefined,[1,2]); // this指向window
    </script>
    <h4>call</h4>
    <div>call方法的第一个参数也是this的指向，后面传入的是一个参数列表</div>
    <br>
    <div>跟apply一样，改变this指向后原函数会立即执行，且此方法只是临时改变this指向一次</div>
    <br>
    <div>同样的，当第一个参数为null、undefined的时候，默认指向window(在浏览器中)</div>
    <script>
      // function fn(...args){
      //     console.log(this,args);
      // }
      // let obj = {
      //     myname:"张三"
      // }
      // fn.call(obj,1,2); // this会变成传入的obj，传入的参数必须是一个列表；
      // fn(1,2) // this指向window
      // // 同样的，当第一个参数为null、undefined的时候，默认指向window(在浏览器中)
      // fn.call(null,[1,2]); // this指向window
      // fn.call(undefined,[1,2]); // this指向window
    </script>
    <h4>bind</h4>
    <div>bind方法和call很相似，第一参数也是this的指向，后面传入的也是一个参数列表(但是这个参数列表可以分多次传入)</div>
    <br>
    <div>改变this指向后不会立即执行，而是返回一个永久改变this指向的函数</div>
    <script>
      // function fn(...args){
      //     console.log(this,args);
      // }
      // let obj = {
      //     myname:"张三"
      // }

      // const bindFn = fn.bind(obj); // this 也会变成传入的obj ，bind不是立即执行需要执行一次
      // bindFn(1,2) // this指向obj
      // fn(1,2) // this指向window
    </script>
    <h2>小结</h2>
    <div>从上面可以看到，apply、call、bind三者的区别在于</div>
    <ul>
      <li>三者都可以改变函数的this对象指向</li>
      <li>三者第一个参数都是this要指向的对象，如果如果没有这个参数或参数为undefined或null，则默认指向全局window</li>
      <li>三者都可以传参，但是apply是数组，而call是参数列表，且apply和call是一次性传入参数，而bind可以分为多次传入</li>
      <li>bind是返回绑定this之后的函数，apply、call 则是立即执行</li>
    </ul>
    <h2>三、实现</h2>
    <div>实现bind的步骤，我们可以分解成为三部分</div>
    <ul>
      <li>修改this指向</li>
      <li>动态传递参数</li>
      <li>兼容new关键字</li>
    </ul>
    <script>
      // 方式一：只在bind中传递函数参数
      // fn.bind(obj, 1, 2)()
      // // // 方式二：在bind中传递函数参数，也在返回函数中传递参数
      // fn.bind(obj, 1)(2)







      // 整体代码
      // Function.prototype.myBind函数的原型上加了一个mybind方法，原型指向Function的函数都可以继承此方法
      // bind方法的使用 let newBing = fn.bind(obj,1)(2)
      // 如下代码告诉我们bind的执行过程
      // 1.let fn = new Function fn拥有mybind方法
      // 2.let fnn = fn.myBind(context) 构造出的fnn函数不在指向Function,而是指向context(从结果上看)

      // 3.从过程上看
      // mybind做了
      Function.prototype.myBind = function (context) {
        // 判断调用对象是否为函数 这个调用对象应该是fn（感觉有问题）
        // 此判断做限制条件，mybind方法只有函数可以调用
        if (typeof this !== "function") {
          throw new TypeError("Error");
        }

        // 获取参数
        // 这个参数在哪？函数都没有接收?
        const args = [...arguments].slice(1),

          // 这步在干嘛？
          fn = this;

        // mybind最终return 一个函数，所以我们这样使用 let fnn = fn.myBind(context)
        return function Fn() {
          // 根据调用方式，传入不同绑定值
          // 1.所以bind方法内部使用啦apply方法？
          return fn.apply(this instanceof Fn ? new fn(...arguments) : context, args.concat(...arguments));
        }
      }

      var obj = {
        a:12,
        b:function say(){
          console.log(this.a,this);
        },
        c:[1,2],
      }
      var obj2 = {
        a:1,
        b:function hello(){
          console.log(this.a);
        }
      }
      // 没有改变this指向的情况下,this指向obj
      var c = obj.b()
      console.log(c); // 12


      // 改变this指向，bind方法不立即执行并返回一个对象，永久生效
      var d = obj.b.bind(obj2)
      // console.log(d.b()); //报错
      const n = obj.b.myBind(obj2)
      d()
      console.log(d,'d');

    </script>
  </div>
</body>

</html>